#!/usr/bin/env bash

set -eEo pipefail

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

# shellcheck source=ci/docker_buildx_commands
source "${SCRIPTPATH}/docker_buildx_commands"

declare -A checksums

DOCKER_MACHINE_VERSION=${DOCKER_MACHINE_VERSION:-0.16.2}
checksums['DOCKER_MACHINE_AMD64']=${DOCKER_MACHINE_AMD64_CHECKSUM:-a7f7cbb842752b12123c5a5447d8039bf8dccf62ec2328853583e68eb4ffb097}
checksums['DOCKER_MACHINE_ARM64']=${DOCKER_MACHINE_ARM64_CHECKSUM:-109f534bfb8b9b852c938cad978e60a86b13f5ecf92da5e24320dacd2a7216ac}
checksums['DOCKER_MACHINE_S390X']="" # No binary available yet for s390x, see https://gitlab.com/gitlab-org/gitlab-runner/-/issues/26551
DUMB_INIT_VERSION=${DUMB_INIT_VERSION:-1.2.2}
checksums['DUMB_INIT_AMD64']=${DUMB_INIT_AMD64_CHECKSUM:-37f2c1f0372a45554f1b89924fbb134fc24c3756efaedf11e07f599494e0eff9}
checksums['DUMB_INIT_ARM64']=${DUMB_INIT_ARM64_CHECKSUM:-45b1bbf56cc03edda81e4220535a025bfe3ed6e93562222b9be4471005b3eeb3}
checksums['DUMB_INIT_S390X']=${DUMB_INIT_S390X_CHECKSUM:-8b3808c3c06d008b8f2eeb2789c7c99e0450b678d94fb50fd446b8f6a22e3a9d}
GIT_LFS_VERSION=${GIT_LFS_VERSION:-2.11.0}
checksums['GIT_LFS_AMD64']=${GIT_LFS_AMD64_CHECKSUM:-f48f45cf8ca600cdde4a8f27b16faf761e2ab944eaf1b0d9a38f66e9979b7df0}
checksums['GIT_LFS_ARM64']=${GIT_LFS_ARM64_CHECKSUM:-b7e65e7f336c465c7049b538f1bc829146f511237f83a55b5d27e51e2e2fcbf7}
checksums['GIT_LFS_S390X']=${GIT_LFS_S390X_CHECKSUM:-8d3d5bb5ed98fe8df788011e890e2d47351ccec8bf47b679576b375840eb0610}

if [ -n "${TARGET_ARCHS}" ]; then
    IFS=', ' read -r -a TARGET_ARCHS <<< "${TARGET_ARCHS}"
else
    TARGET_ARCHS=('amd64')
fi

CI_COMMIT_TAG=${CI_COMMIT_TAG:-}
CI_REGISTRY=${CI_REGISTRY:-}
CI_REGISTRY_IMAGE=${CI_REGISTRY_IMAGE:-}
CI_REGISTRY_USER=${CI_REGISTRY_USER:-}
CI_REGISTRY_PASSWORD=${CI_REGISTRY_PASSWORD:-}
DOCKER_HUB_NAMESPACE=${DOCKER_HUB_NAMESPACE:-'gitlab'}
DOCKER_HUB_USER=${DOCKER_HUB_USER:-}
DOCKER_HUB_PASSWORD=${DOCKER_HUB_PASSWORD:-}
ECR_PUBLIC_REGISTRY=${ECR_PUBLIC_REGISTRY:-'public.ecr.aws/gitlab'}
ECR_PUBLIC_USER='AWS'
ECR_PUBLIC_PASSWORD=${ECR_PUBLIC_PASSWORD:-}
IS_LATEST=${IS_LATEST:-}
PUBLISH_IMAGES=${PUBLISH_IMAGES:-false}
PUSH_TO_DOCKER_HUB=${PUSH_TO_DOCKER_HUB:-false}
PUSH_TO_ECR_PUBLIC=${PUSH_TO_ECR_PUBLIC:-false}

ref_tag="${CI_COMMIT_TAG}"
if [[ -z "${ref_tag}" ]]; then
    ref_tag=${CI_COMMIT_REF_SLUG:-master}
fi

if [[ "${ref_tag}" == "master" ]]; then
    ref_tag=bleeding
fi

REVISION=${REVISION:-}
if [[ -z "${REVISION}" ]]; then
    REVISION=$(git rev-parse --short=8 HEAD || echo "unknown")
fi

join_by() {
    local IFS="$1"
    shift
    echo "$*"
}

# buildx receives an array of tag names, and the context path as the last parameter
buildx() {
    local contextPath="$1"
    local platforms=()
    local os
    os=$(_docker version -f '{{.Server.Os}}')
    for arch in "${TARGET_ARCHS[@]}"; do
        platforms+=("${os}/${arch}")
    done
    shift

    local args=("$@")
    local buildxFlags=()

    local experimental
    experimental=$(_docker_experimental info -f '{{json .ExperimentalBuild}}')
    if [ "${experimental}" = 'false' ]; then
        echo "Docker experimental mode needs to be enabled for multi-platform build support. Aborting."
        echo "See https://github.com/docker/cli/blob/master/experimental/README.md#use-docker-experimental for more information."
        exit 1
    fi

    # Build -t tag name options from remaining arguments
    local tagOpts=()
    for tagName in "${args[@]}"; do
        tagOpts+=("--tag" "${tagName}")
    done

    if [[ "${PUBLISH_IMAGES}" == "true" ]]; then
        echo -e "\033[1mBuilding and pushing image: \033[32m${contextPath}\033[0m"
        buildxFlags+=("--push")
    else
        # If not pushing, just load the resulting image to local Docker
        if [ ${#TARGET_ARCHS[@]} -eq 1 ]; then
            echo -e "\033[1mBuilding and loading image: \033[32m${contextPath}\033[0m"
            # But that is only possible if we are targeting a single platform
            buildxFlags+=("--load")
        else
            echo -e "\033[1mBuilding image: \033[32m${contextPath}\033[0m"
        fi
    fi

    trap cleanup_docker_context_trap ERR SIGINT SIGTERM
    setup_docker_context

    if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${CI_REGISTRY_USER}" ]] && [[ -n "${CI_REGISTRY_PASSWORD}" ]]; then
        login "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
    fi
    if [[ "${PUSH_TO_DOCKER_HUB}" == "true" ]] && [[ -n "${DOCKER_HUB_USER}" ]] && [[ -n "${DOCKER_HUB_PASSWORD}" ]]; then
        login "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}"
    fi
    if [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]] && [[ -n "${ECR_PUBLIC_USER}" ]] && [[ -n "${ECR_PUBLIC_PASSWORD}" ]]; then
        login "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_PASSWORD}" "${ECR_PUBLIC_REGISTRY}"
    fi

    local formatted_platforms
    formatted_platforms=$(join_by , "${platforms[@]}")
    _docker_buildx build \
        --build-arg DOCKER_MACHINE_VERSION="${DOCKER_MACHINE_VERSION}" \
        --build-arg DUMB_INIT_VERSION="${DUMB_INIT_VERSION}" \
        --build-arg GIT_LFS_VERSION="${GIT_LFS_VERSION}" \
        --platform "${formatted_platforms}" \
        --no-cache \
        "${tagOpts[@]}" \
        "${buildxFlags[@]}" \
        "${contextPath}"
    trap - ERR SIGINT SIGTERM
    cleanup_docker_context

    if [[ -z "${PUBLISH_IMAGES}" ]] || [[ "${PUBLISH_IMAGES}" != "true" ]]; then
        echo "Skipping images pushing"
    fi

    if [[ "${PUSH_TO_DOCKER_HUB}" == "true" ]] && [[ -n "${DOCKER_HUB_USER}" ]] && [[ -n "${DOCKER_HUB_PASSWORD}" ]]; then
        logout
    fi
    if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${CI_REGISTRY_USER}" ]] && [[ -n "${CI_REGISTRY_PASSWORD}" ]]; then
        logout "${CI_REGISTRY}"
    fi
    if [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]] && [[ -n "${ECR_PUBLIC_USER}" ]] && [[ -n "${ECR_PUBLIC_PASSWORD}" ]]; then
        logout "${ECR_PUBLIC_REGISTRY}"
    fi
}

import() {
    echo -e "\033[1mImporting image: \033[32m${2}\033[0m"
    _docker import "${1}" "${2}"
}

tag() {
    echo -e "\033[1mTagging image: \033[32m${2}\033[0m"
    _docker tag "${1}" "${2}"
}

tag_latest() {
    if [[ -z "${IS_LATEST}" ]]; then
        return
    fi

    tag "${@}"
}

push() {
    echo -e "\033[1mPushing image: \033[32m${1}\033[0m"
    _docker push "${1}"
}

push_latest() {
    if [[ -z "${IS_LATEST}" ]]; then
        return
    fi

    push "${@}"
}

# Release the helper images. The first passed argument will be used as a
# prefix for the image name, which is useful to push do a different registry.
# For example `registry.gitlab.com/gitlab-org/gitlab-runner` and `gitlab`.
release_docker_helper_images() {
    helper_image_x86_64="${1}/gitlab-runner-helper:x86_64-${REVISION}"
    helper_image_x86_64_version="${1}/gitlab-runner-helper:x86_64-${ref_tag}"
    helper_image_x86_64_latest="${1}/gitlab-runner-helper:x86_64-latest"
    helper_image_arm="${1}/gitlab-runner-helper:arm-${REVISION}"
    helper_image_arm_version="${1}/gitlab-runner-helper:arm-${ref_tag}"
    helper_image_arm_latest="${1}/gitlab-runner-helper:arm-latest"
    helper_image_arm64="${1}/gitlab-runner-helper:arm64-${REVISION}"
    helper_image_arm64_version="${1}/gitlab-runner-helper:arm64-${ref_tag}"
    helper_image_arm64_latest="${1}/gitlab-runner-helper:arm64-latest"
    helper_image_s390x="${1}/gitlab-runner-helper:s390x-${REVISION}"
    helper_image_s390x_version="${1}/gitlab-runner-helper:s390x-${ref_tag}"
    helper_image_s390x_latest="${1}/gitlab-runner-helper:s390x-latest"

    import out/helper-images/prebuilt-x86_64.tar.xz "${helper_image_x86_64}"
    import out/helper-images/prebuilt-arm.tar.xz "${helper_image_arm}"
    import out/helper-images/prebuilt-arm64.tar.xz "${helper_image_arm64}"
    import out/helper-images/prebuilt-s390x.tar.xz "${helper_image_s390x}"

    tag_latest "${helper_image_x86_64}" "${helper_image_x86_64_latest}"
    tag_latest "${helper_image_arm}" "${helper_image_arm_latest}"
    tag_latest "${helper_image_arm64}" "${helper_image_arm64_latest}"
    tag_latest "${helper_image_s390x}" "${helper_image_s390x_latest}"
    tag "${helper_image_x86_64}" "${helper_image_x86_64_version}"
    tag "${helper_image_arm}" "${helper_image_arm_version}"
    tag "${helper_image_arm64}" "${helper_image_arm64_version}"
    tag "${helper_image_s390x}" "${helper_image_s390x_version}"

    push "${helper_image_x86_64}"
    push "${helper_image_arm}"
    push "${helper_image_arm64}"
    push "${helper_image_s390x}"

    push_latest "${helper_image_x86_64_latest}"
    push_latest "${helper_image_arm_latest}"
    push_latest "${helper_image_arm64_latest}"
    push_latest "${helper_image_s390x_latest}"
    push "${helper_image_x86_64_version}"
    push "${helper_image_arm_version}"
    push "${helper_image_arm64_version}"
    push "${helper_image_s390x_version}"
}

login() {
    echo "${2}" | _docker login --username "${1}" --password-stdin "${3}"
}

logout() {
    _docker logout "${1}"
}

add_tags() {
    local -n tags=$1
    local base_image="$2"
    local user="$3"
    local password="$4"
    local repository="$5"
    local default_image='ubuntu'

    if [[ -z "${user}" ]] || [[ -z "${password}" ]]; then
        return
    fi

    tags+=("${repository}:${base_image}-${ref_tag}")
    if [[ "${base_image}" == "${default_image}" ]]; then
        tags+=("${repository}:${ref_tag}")
    fi
    if [[ -n "${IS_LATEST}" ]]; then
        tags+=("${repository}:${base_image}")
        if [[ "${base_image}" == "${default_image}" ]]; then
            tags+=("${repository}:latest")
        fi
    fi
}

[ "${#TARGET_ARCHS[@]}" -eq 0 ] && TARGET_ARCHS=("$(_docker version -f '{{.Server.Arch}}')")

runner_home_dir="dockerfiles/runner"

function writeChecksum() {
  local binVarNamePrefix="$1"
  local targetArch="$2"
  local binFile="$3"
  local archVarNameSuffix=$(echo "${targetArch}" | tr '[:lower:]' '[:upper:]')
  local checksum="${checksums["${binVarNamePrefix}_${archVarNameSuffix}"]}"

  [[ -n "${checksum}" ]] && echo "${checksum}  ${binFile}" >> "${runner_home_dir}/checksums-${targetArch}" || return 0
}

for arch in "${TARGET_ARCHS[@]}"; do
    echo "${arch}:"
    rm -f "${runner_home_dir}/checksums-${arch}"
    writeChecksum 'DOCKER_MACHINE' "${arch}" '/usr/bin/docker-machine'
    writeChecksum 'DUMB_INIT' "${arch}" '/usr/bin/dumb-init'
    writeChecksum 'GIT_LFS' "${arch}" '/usr/bin/git-lfs'
done

cp "${runner_home_dir}/install-deps" "${runner_home_dir}/ubuntu/"
cp "${runner_home_dir}/install-deps" "${runner_home_dir}/alpine/"
for arch in "${TARGET_ARCHS[@]}"; do
    cp "${runner_home_dir}/checksums-${arch}" "out/deb/gitlab-runner_${arch}.deb" \
        "${runner_home_dir}/ubuntu/"
    cp "${runner_home_dir}/checksums-${arch}" "out/binaries/gitlab-runner-linux-${arch}" \
        "${runner_home_dir}/alpine/"
done

alpineTags=()
ubuntuTags=()
if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ "${PUSH_TO_DOCKER_HUB}" == "true" ]]; then
   add_tags alpineTags "alpine" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" "${DOCKER_HUB_NAMESPACE}/gitlab-runner" "${ref_tag}"
   add_tags ubuntuTags "ubuntu" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" "${DOCKER_HUB_NAMESPACE}/gitlab-runner" "${ref_tag}"
fi

if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${CI_REGISTRY}" ]] && [[ -n "${CI_REGISTRY_IMAGE}" ]]; then
   add_tags alpineTags "alpine" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY_IMAGE}" "${ref_tag}"
   add_tags ubuntuTags "ubuntu" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY_IMAGE}" "${ref_tag}"
fi

if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]]; then
   add_tags alpineTags "alpine" "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_PASSWORD}" "${ECR_PUBLIC_REGISTRY}/gitlab-runner" "${ref_tag}"
   add_tags ubuntuTags "ubuntu" "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_PASSWORD}" "${ECR_PUBLIC_REGISTRY}/gitlab-runner" "${ref_tag}"
fi

# Build and publish multi-platform images using `docker buildx`
buildx "${runner_home_dir}/ubuntu" "${ubuntuTags[@]}"
buildx "${runner_home_dir}/alpine" "${alpineTags[@]}"

# Release helper images
if [[ -n "${CI_REGISTRY_USER}" ]] && [[ -n "${CI_REGISTRY_PASSWORD}" && "${PUBLISH_IMAGES}" == "true" ]]; then
    login "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"

    release_docker_helper_images "${CI_REGISTRY_IMAGE}"

    logout "${CI_REGISTRY}"
fi

if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ "${PUSH_TO_DOCKER_HUB}" == "true" ]]; then
    login "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}"

    release_docker_helper_images "${DOCKER_HUB_NAMESPACE}"

    logout
fi

if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]]; then
    login "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_PASSWORD}" "${ECR_PUBLIC_REGISTRY}"

    release_docker_helper_images "${ECR_PUBLIC_REGISTRY}"

    logout "${ECR_PUBLIC_REGISTRY}"
fi
